package com.wva.captcha.service.impl;

import com.google.code.kaptcha.Producer;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import com.wva.captcha.constant.CaptchaConst;
import com.wva.captcha.domain.dto.GetSmsCaptchaDTO;
import com.wva.captcha.domain.dto.SmsCaptchaSendDTO;
import com.wva.captcha.domain.vo.GetPictureCaptchaVO;
import com.wva.captcha.enums.QueueEnum;
import com.wva.captcha.service.CaptchaService;
import com.wva.common.domain.dto.PictureCaptchaVerifyDTO;
import com.wva.common.domain.dto.SmsCaptchaVerifyDTO;
import com.wva.common.exception.BusinessException;
import com.wva.common.exception.SystemException;
import com.wva.common.status.StatusEnum;
import com.wva.common.utils.Assert;
import com.wva.common.utils.RandomUtils;
import com.wva.redis.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.FastByteArrayOutputStream;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class CaptchaServiceImpl implements CaptchaService {
    @Autowired
    Producer kaptchaProducer;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    RedisTemplate<String, Object> redisTemplate;


    private static final DefaultRedisScript<Long> verifyPictureCaptchaScript =
            RedisUtils.getScriptByPath("/lua/pictureVerify.lua", Long.class);
    private static final DefaultRedisScript<Long> getSmsCaptchScript =
            RedisUtils.getScriptByPath("/lua/getSmsCaptcha.lua", Long.class);
    private static final DefaultRedisScript<Long> verifySmsCaptchScript =
            RedisUtils.getScriptByPath("/lua/verifySmsCaptcha.lua", Long.class);

    @Override
    public GetPictureCaptchaVO getPictureCaptcha() {
        // 生成验证码
        String captchaText = kaptchaProducer.createText();
        BufferedImage captchaImage = kaptchaProducer.createImage(captchaText);
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        try {
            ImageIO.write(captchaImage, "jpg", os);
        } catch (IOException e) {
            throw new SystemException(StatusEnum.INTERNAL_ERROR);
        }
        String captchaImageBase64 = Base64.encode(os.toByteArray());

        // 将验证码文本保存到redis
        String captchaKey = RandomUtils.getRandomId();
        String key = String.format("captcha:picture:%s", captchaKey);
        stringRedisTemplate.opsForValue().set(
                key,
                captchaText,
                CaptchaConst.PICTURE_CAPTCHA_TIMEOUT,
                TimeUnit.SECONDS
        );

        return GetPictureCaptchaVO.builder()
                .picture(captchaImageBase64)
                .key(captchaKey)
                .build();
    }

    @Override
    public void verifyPictureCaptcha(PictureCaptchaVerifyDTO pictureCaptchaVerifyDTO) {
        Long status = stringRedisTemplate.execute(
                verifyPictureCaptchaScript,
                Collections.singletonList(pictureCaptchaVerifyDTO.getKey()),
                pictureCaptchaVerifyDTO.getCode()
        );
        Assert.notNull(status);

        // 根据key从redis读取验证码文本
        if (status == 1) {
            throw new BusinessException("验证码不存在");
        }

        // 比对验证码是否正确
        if (status == 2) {
            throw new BusinessException("验证码错误");
        }
    }

    @Override
    public void getSmsCaptcha(GetSmsCaptchaDTO getSmsCaptchaDTO) {
        //校验验证码
        verifyPictureCaptcha(getSmsCaptchaDTO.getPictureVerify());

        //执行lua脚本
        String phone = getSmsCaptchaDTO.getPhone();
        int code = RandomUtils.getRandomNumber(100000, 1000000);
        Long result = stringRedisTemplate.execute(
                getSmsCaptchScript,
                Collections.singletonList(phone),
                code + "",
                CaptchaConst.SMS_CAPTCHA_INTERVAL + "",
                CaptchaConst.SMS_CAPTCHA_TIMEOUT + ""
        );
        Assert.notNull(result);

        //异常情况
        if (result == 1) {
            throw new BusinessException("还不可以发送验证码");
        }

        //发送到消息队列
        SmsCaptchaSendDTO smsCaptchaSendDTO = new SmsCaptchaSendDTO(phone, code);
        rabbitTemplate.convertAndSend(
                QueueEnum.SMS_CAPTCHA_SEND.getExchange(),
                QueueEnum.SMS_CAPTCHA_SEND.getRoutingKey(),
                smsCaptchaSendDTO
        );


    }

    @Override
    public void verifySmsCaptcha(SmsCaptchaVerifyDTO smsCaptchaVerifyDTO) {
        //执行lua脚本
        String phone = smsCaptchaVerifyDTO.getPhone();
        String code = smsCaptchaVerifyDTO.getCode();
        Long result = stringRedisTemplate.execute(
                verifySmsCaptchScript,
                Collections.singletonList(phone),
                code,
                CaptchaConst.SMS_CAPTCHA_MAX_ERROR_COUNT+""
        );
        Assert.notNull(result);

        //错误判断
        if (result == 1) throw new BusinessException("请先获取验证码");
        if (result == 2) throw new BusinessException("验证码失效，请重新获取");
        if (result == 3) throw new BusinessException("验证码错误");
        if (result != 0) throw new SystemException(StatusEnum.INTERNAL_ERROR);

        //校验成功
        //...
    }
}
